#pragma once

#include <stx/core/print.hpp>

inline namespace stx
{
[[nodiscard]] constexpr inline bool startsWith(IsStringLike auto const& s,
                                               IsStringLike auto const& ss)
{
    static_assert(
        IsIdentical<StringCharType<decltype(s)>, StringCharType<decltype(ss)>>);

    if constexpr (IsStdZstring<decltype(s)> && IsStdZstring<decltype(ss)>)
    {
        auto const* p1 = &s[0];
        auto const* p2 = &ss[0];

        while (*p1 && *p2)
        {
            if (*p1 != *p2)
            {
                return false;
            }

            ++p1;
            ++p2;
        }

        return 0 == *p2;
    }
    else if constexpr (IsStdZstring<decltype(s)> && !IsStdZstring<decltype(ss)>)
    {
        auto const* p1       = &s[0];
        auto const  sv2      = tosv(ss);
        auto const  sv2_size = sv2.size();
        auto        i        = 0uz;

        while (*p1 && i < sv2_size)
        {
            if (*p1 != sv2[i])
            {
                return false;
            }

            ++p1;
            ++i;
        }

        return sv2_size == i;
    }
    else if constexpr (!IsStdZstring<decltype(s)> && IsStdZstring<decltype(ss)>)
    {
        auto const  sv1      = tosv(s);
        auto const* p2       = &ss[0];
        auto const  sv1_size = sv1.size();
        auto        i        = 0uz;

        while (*p2 && i < sv1_size)
        {
            if (*p2 != sv1[i])
            {
                return false;
            }

            ++p2;
            ++i;
        }

        return 0 == *p2;
    }
    else
    {
        return tosv(s).starts_with(ss);
    }
}

[[nodiscard]] constexpr inline bool endsWith(IsStringLike auto const& s,
                                             IsStringLike auto const& ss)
{
    static_assert(
        IsIdentical<StringCharType<decltype(s)>, StringCharType<decltype(ss)>>);

    return tosv(s).ends_with(ss);
}

[[nodiscard]] constexpr inline bool
contains(ranges::input_range auto const& coll, IsNonVoid auto const& value)
{
    using T = decltype(coll);
    using U = decltype(value);

    if constexpr (requires { IS_SAME_AS(bool, coll.contains(value)); })
    {
        return coll.contains(value);
    }
    else if constexpr (EXPR(IsExStdString<T> || IsExStdStringView<T>) &&
                       IsStringLike<U>)
    {
        if constexpr (IsIdentical<StringCharType<T>, StringCharType<U>>)
        {
            return coll.find(value) != coll.npos;
        }
        else
        {
            static_assert(AlwaysFalse<T>);
        }
    }
    else if constexpr (requires {
                           requires IsIdentical<decltype(coll.end() ==
                                                         coll.find(value)),
                                                bool>;
                       })
    {
        return coll.find(value) != coll.end();
    }
    else
    {
        static_assert(AlwaysFalse<T>);
    }
}

template<IsStdInsertableSequenceContainer Container_, IsNonVoid... Args_>
requires requires
{
    requires std::constructible_from<typename Container_::value_type,
                                     Args_&&...>;
}
inline Container_& insert(Container_& coll, Args_&&... args)
{
    using T = NoCvr<Container_>;

    if constexpr (IsStdStack<T> || IsStdQueue<T> || IsStdPriorityQueue<T> ||
                  IsStdMultiset<T> || IsStdUnorderedMultiset<T>)
    {
        coll.emplace(FORWARD(args)...);
    }
    else if constexpr (IsStdSet<T>)
    {
        using ValueType_ = NoCvr<RangeValueType<Container_>>;

        auto       value = ValueType_(FORWARD(args)...);
        auto const pos   = coll.lower_bound(value);

        if (pos != coll.end() && *pos == value)
        {
            coll.emplace_hint(coll.erase(pos), std::move(value));
        }
        else
        {
            coll.emplace_hint(pos, std::move(value));
        }
    }
    else if constexpr (IsStdUnorderedSet<T>)
    {
        using ValueType_ = NoCvr<RangeValueType<Container_>>;

        auto value               = ValueType_(FORWARD(args)...);
        auto const [first, last] = coll.equal_range(value);
        coll.emplace_hint(first, std::move(value));
    }
    else if constexpr (IsExStdString<T>)
    {
        static_assert(1 == sizeof...(args));
        coll.push_back(static_cast<StringCharType<T>>(FORWARD(args))...);
    }
    else if constexpr (IsStdVector<T> || IsStdDeque<T> || IsStdList<T>)
    {
        coll.emplace_back(FORWARD(args)...);
    }
    else if constexpr (IsStdForwardList<T>)
    {
        coll.emplace_front(FORWARD(args)...);
    }
    else
    {
        static_assert(AlwaysFalse<Container_>);
    }

    return coll;
}

template<IsOfStdSetFamily Container_, IsNonVoid... Args_>
requires requires
{
    requires !IsIdentical<Head<Args_&&...>, typename Container_::node_type>;
    requires std::constructible_from<typename Container_::value_type,
                                     Args_&&...>;
}
inline Container_& insert(Container_& coll, Args_&&... args)
{
    coll.emplace(FORWARD(args)...);
    return coll;
}

template<IsOfStdMapFamily Container_, IsNonVoid Key_, IsNonVoid... Args_>
requires requires(Container_& coll, Key_&& key, Args_&&... args)
{
    requires std::constructible_from<typename Container_::key_type, Key_&&>;
    requires std::constructible_from<typename Container_::mapped_type,
                                     Args_&&...>;
}
inline Container_& insert(Container_& coll, Key_&& key, Args_&&... args)
{
    using KeyType_    = typename Container_::key_type;
    using MappedType_ = typename Container_::mapped_type;

    coll.emplace(
        std::pair<KeyType_ const, MappedType_>(FORWARD(key), FORWARD(args)...));
    return coll;
}

template<IsStdAssociativeContainer Container_>
inline Container_& insert(Container_&                            coll,
                          Type<typename Container_::node_type>&& arg)
{
    coll.insert(std::move(arg));
    return coll;
}

template<IsStdInsertableContainer Container_,
         IsInputRange             HeadRange_,
         IsInputRange... TailRanges_>
requires requires
{
    requires !IsOfStdMapFamily<Container_>;
    requires std::constructible_from<typename Container_::value_type,
                                     RangeValueType<HeadRange_>>;
}
inline Container_&
splice(Container_& coll, HeadRange_&& head, TailRanges_&&... tail)
{
    using T = Container_;

    if constexpr (IsStdAssociativeContainer<T>)
    {
        if constexpr (IsRvRef<decltype(head)>)
        {
            coll.insert(std::make_move_iterator(head.begin()),
                        std::make_move_iterator(head.end()));
        }
        else
        {
            coll.insert(std::begin(head), std::end(head));
        }
    }
    else if constexpr (IsStdList<T>)
    {
        if constexpr (IsIdentical<decltype(coll), decltype(head)> &&
                      IsRvRef<decltype(head)>)
        {
            coll.splice(coll.end(), head);
        }
        else
        {
            if constexpr (IsRvRef<decltype(head)>)
            {
                for (auto&& elem : head)
                {
                    insert(coll, std::move(elem));
                }
            }
            else
            {
                for (auto&& elem : head)
                {
                    insert(coll, elem);
                }
            }
        }
    }
    else if constexpr (IsStdForwardList<T>)
    {
        if constexpr (IsIdentical<T, decltype(head)> && IsRvRef<decltype(head)>)
        {
            coll.splice_after(coll.before_begin(), head);
        }
        else
        {
            if constexpr (IsRvRef<decltype(head)>)
            {
                for (auto&& elem : head)
                {
                    insert(coll, std::move(elem));
                }
            }
            else
            {
                for (auto&& elem : head)
                {
                    insert(coll, elem);
                }
            }
        }
    }
    else if constexpr (IsStdStack<T> || IsStdQueue<T> || IsStdPriorityQueue<T>)
    {
        if constexpr (IsRvRef<decltype(head)>)
        {
            for (auto&& v : head)
            {
                insert(coll, std::move(v));
            }
        }
        else
        {
            for (auto const& v : head)
            {
                insert(coll, v);
            }
        }
    }
    else
    {
        if constexpr (IsRvRef<decltype(head)>)
        {
            coll.insert(coll.end(),
                        std::make_move_iterator(head.begin()),
                        std::make_move_iterator(head.end()));
        }
        else
        {
            coll.insert(coll.end(), std::begin(head), std::end(head));
        }
    }

    if constexpr (sizeof...(tail))
    {
        return splice(coll, FORWARD(tail)...);
    }
    else
    {
        return coll;
    }
}

template<IsNonVoid Container_, IsNonVoid Value_>
requires requires(Container_& coll, Value_ const& value)
{
    requires IsStdInsertableContainer<Container_>;
    requires IsIdentical<decltype(coll.begin()), decltype(coll.find(value))>;
}
inline std::size_t eraseAll(Container_& coll, Value_ const& value)
{
    using T = NoCvr<decltype(coll)>;
    if constexpr (IsStdAssociativeContainer<T>)
    {
        auto const old_size = coll.size();

        if constexpr (IsStdMultiAssociativeContainer<T>)
        {
            auto const [first, last] = coll.equal_range(value);
            coll.erase(first, last);
        }
        else
        {
            auto const pos = coll.find(value);
            if (pos != coll.end())
            {
                coll.erase(pos);
            }
        }

        return old_size - coll.size();
    }
    else if constexpr (IsStdVector<T> || IsExStdString<T> || IsStdDeque<T>)
    {
        auto const old_size = coll.size();
        coll.erase(std::remove(coll.begin(), coll.end(), value), coll.end());
        return old_size - coll.size();
    }
    else if constexpr (IsStdList<T> || IsStdForwardList<T>)
    {
        return coll.remove(value);
    }
    else
    {
        static_assert(AlwaysFalse<T>);
    }
}

inline std::size_t eraseIf(IsStdInsertableContainer auto& coll, auto pr1)
{
    using T = NoCvr<decltype(coll)>;
    if constexpr (IsStdAssociativeContainer<T>)
    {
        auto const old_size = coll.size();
        auto       pos = coll.begin(), last = coll.end();

        while (pos != last)
        {
            if (pr1(*pos))
            {
                pos = coll.erase(pos);
                continue;
            }

            ++pos;
        }

        return old_size - coll.size();
    }
    else if constexpr (IsStdVector<T> || IsExStdString<T> || IsStdDeque<T>)
    {
        auto const old_size = coll.size();
        coll.erase(std::remove_if(coll.begin(), coll.end(), pr1), coll.end());
        return old_size - coll.size();
    }
    else if constexpr (IsStdList<T> || IsStdForwardList<T>)
    {
        return coll.remove_if(pr1);
    }
    else
    {
        static_assert(AlwaysFalse<T>);
    }
}

template<IsStdAssociativeContainer Container_, IsNonVoid Key_>
requires requires(Container_& coll, Key_ const& key)
{
    requires !IsConst<Container_&>;
    IS_CONVERTIBLE_TO(bool, coll.end() != coll.find(key));
}
[[nodiscard]] typename Container_::node_type extract(Container_& coll,
                                                     Key_ const& key)
{
    auto const pos = coll.find(key);
    if (pos != coll.end())
    {
        return coll.extract(pos);
    }

    return {};
}

template<std::forward_iterator FwdIter_, bool C_IS_OF_STD_MAP_FAMILY_>
class AssociativeContainerQueryResult__ final
{
    FwdIter_ pos_;
    bool     valid_ = false;

public:
    AssociativeContainerQueryResult__() noexcept = default;

    AssociativeContainerQueryResult__(FwdIter_ const& pos, bool const valid)
        : pos_(pos)
        , valid_(valid)
    {}

    [[nodiscard]] explicit operator bool() const noexcept
    {
        return this->valid_;
    }

    // map containers only
    [[nodiscard]] auto const& key() const
    {
        ASSERT(this->valid_);
        if constexpr (C_IS_OF_STD_MAP_FAMILY_)
        {
            return this->pos_->first;
        }
        else
        {
            static_assert(AlwaysFalse<FwdIter_>);
        }
    }

    // map containers only
    [[nodiscard]] auto& mapped()
    {
        if constexpr (C_IS_OF_STD_MAP_FAMILY_)
        {
            ASSERT(this->valid_);
            return this->pos_->second;
        }
        else
        {
            static_assert(AlwaysFalse<FwdIter_>);
        }
    }

    // map containers only
    [[nodiscard]] auto const& mapped() const
    {
        if constexpr (C_IS_OF_STD_MAP_FAMILY_)
        {
            ASSERT(this->valid_);
            return this->pos_->second;
        }
        else
        {
            static_assert(AlwaysFalse<FwdIter_>);
        }
    }

    // set containers only
    [[nodiscard]] auto const& value() const
    {
        if constexpr (!C_IS_OF_STD_MAP_FAMILY_)
        {
            ASSERT(this->valid_);
            return *this->pos_;
        }
        else
        {
            static_assert(AlwaysFalse<FwdIter_>);
        }
    }
};

template<IsStdAssociativeContainer T>
requires EXPR(!IsRvRef<T&&>)
[[nodiscard]] auto query(T&& coll, auto const& key)
{
    auto pos = coll.find(key);
    return AssociativeContainerQueryResult__<NoCvr<decltype(pos)>,
                                             IsOfStdMapFamily<decltype(coll)>>(
        pos, pos != coll.end());
}
} // namespace stx